Skip to content

Improve event-driven reporter with cycle detection - #31

Merged
0xSpuddy merged 6 commits into
tellor-io:mainfrom
cryptoriums:perf/event-driven-cycle-monitor
Jun 25, 2026
Merged

Improve event-driven reporter with cycle detection#31
0xSpuddy merged 6 commits into
tellor-io:mainfrom
cryptoriums:perf/event-driven-cycle-monitor

Conversation

@diorwave

Copy link
Copy Markdown
Contributor

Summary

Replaces the 200ms polling loop in MonitorCyclelistQuery with a CometBFT NewBlock WebSocket subscription. The reporter now reacts to each new block within ~10-50ms instead of waiting up to 200ms, giving more of the 3.1s cycle window for price fetching, tx building, and broadcasting.

Changes

  • Store rpcClient (*rpchttp.HTTP) directly on Client to access the Subscribe API
  • MonitorCyclelistQuery subscribes to tm.event='NewBlock' on startup
  • 2-second fallback timer fires if no block event is received (handles slow nodes)
  • Graceful fallback to original 200ms ticker polling if the WebSocket subscription fails or the channel closes
  • Original polling logic preserved as monitorCyclelistQueryPolling()

Expected Impact

Reduces cycle detection latency by ~150ms on average. Combined with the existing 15s broadcast timeout, this is expected to bring steady-state miss rates from ~11% closer to the 2-4% range.

@0xSpuddy
0xSpuddy self-requested a review June 2, 2026 19:32

@0xSpuddy 0xSpuddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like these changes, but want to change setRPCClient(...) to give the concrete HTTP client used for subscriptions the same fallback that you get when normal RPC fallback switches endpoints. (feel free to pull in the changes from my branch here or I can merge it all in tomorrow after manual test on palmito.

Comment thread reporter/client/client.go
grpcConn *grpc.ClientConn
grpcClient daemontypes.GrpcClient

rpcClient *rpchttp.HTTP // direct reference for WebSocket subscriptions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this import in a new branch pr-31-review

defer wg.Done()

// Try to use event-driven detection (reacts within milliseconds of each new block).
// Falls back to 200ms ticker polling if the WebSocket subscription fails.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added fallback behavior to next listed rpc in .env in this branch https://github.com/tellor-io/layer-daemons/tree/pr-31-review

@0xSpuddy 0xSpuddy mentioned this pull request Jun 3, 2026
Comment thread reporter/client/client.go Outdated
@diorwave diorwave closed this Jun 22, 2026
@diorwave diorwave reopened this Jun 22, 2026

@0xSpuddy 0xSpuddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's just import ordering, so I will approve the pr and merge.

// Channel closed; try to re-subscribe on a fallback RPC endpoint.
unsubscribe()
c.logger.Warn("block subscription channel closed, trying fallback RPC endpoint")
blockCh, unsubscribe, err = c.subscribeNewBlocks(ctx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest using subscribeNewBlocksFallback instead to make consistent with existing fallback behavior

blockCh, unsubscribe, err = c.subscribeNewBlocksFallback(ctx, fmt.Errorf("subscription channel closed"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a separate resource cleanup issue that we can solve after this is merged, or as part of this work: rpcEndpointManager creates fresh RPC clients, and the WebSocket path starts them with httpClient.Start(), but the cleanup only unsubscribes and never calls Stop(). When a subscription is replaced, a fallback endpoint is tried, Subscribe fails after Start(), or the primary RPC health check starts a client and then rejects it, the old CometBFT client workers/connections appear to be left running. Can we make the owner of each started RPC client stop it when it is no longer used?

Comment thread reporter/client/client.go
@@ -14,6 +14,7 @@ import (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please address lint check

@0xSpuddy
0xSpuddy merged commit c3778d7 into tellor-io:main Jun 25, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants